-- The MIT License
-- 
-- Copyright (c) 2008 Matthew Maravillas, Patrick Quinn
-- 
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
-- 
-- The above copyright notice and this permission notice shall be included in
-- all copies or substantial portions of the Software.
-- 
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-- THE SOFTWARE.

local util = import('/mods/koth/hook/lua/util.lua')
local Hill = import('/mods/koth/hook/lua/hill.lua').Hill
local Flag = import('/mods/koth/hook/lua/flag.lua').Flag
local KothOptions = import('/mods/koth/hook/lua/kothOptions.lua').KothOptions
local ControllingUnitsFunc = import('/mods/koth/hook/lua/kothOptions.lua').ControllingUnitsFunc

-- Delay between polls for hill presence, in seconds
UPDATE_DELAY = 2

ArmyKothStatus = Class() {
    __init = function(self)
        self.previousOwnershipTime = 0
        self.isAloneOnHill = false
        self.currentOwnershipStartTime = nil
    end,
      
    -- Returns the amount of uninterrupted time the army has held the hill
    HillOwnershipTime = function(self)
        local totalTime = self.previousOwnershipTime
        if self.isAloneOnHill then
            totalTime = totalTime + GetGameTimeSeconds() - self.currentOwnershipStartTime
        end
        return math.floor(totalTime)
    end,

    TakeHill = function(self)
        self.currentOwnershipStartTime = GetGameTimeSeconds()
        self.isAloneOnHill = true
    end,
   
    LoseHill = function(self)
        if self.isAloneOnHill then
            self.previousOwnershipTime = self:HillOwnershipTime()
            self.isAloneOnHill = false
            self.currentOwnershipStartTime = nil
        end
    end,
}


KingOfTheHillGame = Class(){  
    __init = function(self)  
        LOG('__init: Initializing KingOfTheHillGame')
        
        -- The armies participating in the King of the Hill game
        self.armies = { }
    
        local allArmies = ListArmies()
        
        for armyIndex, armyName in pairs(allArmies) do
            local isArmyCivilian = ScenarioInfo.ArmySetup[armyName].Civilian
            
            if not isArmyCivilian then
                table.insert(self.armies, armyName)
                
                -- Initialize the score counter for this army
                local brain = GetArmyBrain(armyName)
                
                brain.kothStatus = ArmyKothStatus()
                brain.CalculateScore = function(self) 
                    return self.kothStatus:HillOwnershipTime()
                end
            end
        end
    end,
    
    IsPlayerAnnihilated = function(self, brain)
        return brain:GetCurrentUnits(categories.ALLUNITS - categories.WALL - categories.KOTHFLAG) == 0
    end,
    
    -- Creates a function for the king of the hill game loop, closing over
    -- self so we can access member variables.
    GetThreadFunc = function(self)
        return function()
            local hill = Hill(KothOptions.HillSize, self.armies, ControllingUnitsFunc[KothOptions.ControllingUnits])

            self:SetupHillStatusMessages(hill)

            ForkThread(function()
                self:SyncHillTimers()
            end)

            ForkThread(function()
                WaitSeconds(KothOptions.HillDelay)
                hill:Open()
            end)
            
            local winner, livingPlayers = self:RunGameLoop(hill)

            -- Notify the winner of his win.
            if winner then
                winner:OnVictory()
            end
            
            -- And notify the losers of their crushing defeat.
            for index, brain in livingPlayers do
                if brain ~= winner then
                    LOG('KingOfTheHillThread: ' .. brain.Name .. ' is a loser')
                    brain:OnDefeat()
                end
            end
            
            -- gameOver is a global from /lua/victory.lua
            gameOver = true
            ForkThread(
                function()
                    WaitSeconds(10)
                    LOG('KingOfTheHillThread: Ending the game')
                    EndGame()
                end)
            end
    end,
    
    RunGameLoop = function(self, hill)    
        local winner = nil
        local livingPlayers = {}
        
        while true do
            LOG('RunGameLoop: Tick')
            
            WaitSeconds(UPDATE_DELAY)

            livingPlayers = {}
            
            -- Look for newly defeated players and tell them they're dead.
            LOG('RunGameLoop: Bringing out the dead')
            for index, brain in ArmyBrains do
                if not brain:IsDefeated() and not ArmyIsCivilian(brain:GetArmyIndex()) then                
                    if self:IsPlayerAnnihilated(brain) then
                        LOG('RunGameLoop: ' .. brain.Name .. ' is dead')
                        brain:OnDefeat()
                    else
                        LOG('RunGameLoop: ' .. brain.Name .. ' is still alive!')
                        table.insert(livingPlayers, brain)
                    end
                end
            end

            -- Nobody's alive. It's a draw.
            if table.empty(livingPlayers) then
                LOG('RunGameLoop: All players dead, declaring a draw')
                break
            end
            
            -- One player left. He's the winnar!
            if KothOptions.AllowAnnihilationVictory and table.getn(livingPlayers) == 1 then
                LOG('RunGameLoop: ' .. livingPlayers[1].Name .. ' won by annhiliation')
                winner = livingPlayers[1]
                break
            end
            
            -- Two or more players are still alive, so add up new hill time.
            local presentArmies = hill:UpdateStatus()

            LOG('RunGameLoop: There are ' .. table.getn(presentArmies) .. ' armies on the hill: ' .. repr(presentArmies))       
            
            -- Only add hill time if the hill is uncontested.
            if table.getn(presentArmies) == 1 then
                local brain = GetArmyBrain(presentArmies[1])
                
                LOG('RunGameLoop: ' .. brain.Name .. ' now has ' .. brain.kothStatus:HillOwnershipTime() .. ' of ' .. KothOptions.TimeOnHillToWin .. ' seconds on the hill')
                
                if brain.kothStatus:HillOwnershipTime() >= KothOptions.TimeOnHillToWin then
                    LOG('RunGameLoop: ' .. brain.Name .. ' won by hill control with ' .. KothOptions.TimeOnHillToWin .. ' or more seconds of hill time')
                    winner = brain
                    break
                end
            end
        end
        
        return winner, livingPlayers
    end,

    SyncHillTimers = function(self)
        while true do
            for index, brain in ArmyBrains do
                if Sync.Score[index] and Sync.Score[index].general then
                    Sync.Score[index].general.score = brain:CalculateScore()
                end
            end
            WaitTicks(1) 
        end
    end,

    SetupHillStatusMessages = function(self, hill) 
        hill:AddArmyArrivedCallback(function(armyName)
            local brain = GetArmyBrain(armyName)
            print(brain.Nickname .. ' arrived on the hill')
        end)

        hill:AddArmyDepartedCallback(function(armyName)
             local brain = GetArmyBrain(armyName)
             brain.kothStatus:LoseHill()
             print(brain.Nickname .. ' is no longer on the hill')
        end)

        hill:AddHillClaimedCallback(function(armyName)
            local brain = GetArmyBrain(armyName)
            brain.kothStatus:TakeHill()
            print(brain.Nickname .. ' is alone on the hill')
        end)

        hill:AddHillContestedCallback(function()
            for index, brain in ArmyBrains do
                if brain.kothStatus then
                    brain.kothStatus:LoseHill()
                end
            end
            print('The hill is now contested')
        end)

        hill:AddHillAbandonedCallback(function()
            print('No one is currently on the hill')
        end)

        hill:AddHillOpenedCallback(function()
            print('The hill can now be claimed')
        end)
    end,

}
